#include "GpuProgram.h"
#include "HighLevelGpuProgramManager.h"
#include "LogManager.h"
#include "StringConverter.h"

#include "GLSLESProgram.h"
#include "GLSLESGpuProgram.h"
#include "GLSLESLinkProgramManager.h"
#include "GLSLESPreprocessor.h"

namespace PVM
{
	std::string operationTypeToString(RenderOperation::OperationType val);
	RenderOperation::OperationType parseOperationType(const std::string& val);
	
	GLSLESProgram::CmdPreprocessorDefines GLSLESProgram::msCmdPreprocessorDefines;
	GLSLESProgram::CmdOptimisation GLSLESProgram::msCmdOptimisation;
	
	GLSLESProgram::GLSLESProgram(ResourceManager* creator,
								 const std::string& name, ResourceHandle handle,
								 const std::string& group, bool isManual, ManualResourceLoader* loader)
	: HighLevelGpuProgram(creator, name, handle, group, isManual, loader)
	, mGLHandle(0)
	, mCompiled(0)
	, mIsOptimised(false)
	, mOptimiserEnabled(true)
	{
		if (createParamDictionary("GLSLESProgram"))
		{
			setupBaseParamDictionary();
			ParamDictionary* dict = getParamDictionary();
			
			dict->addParameter(ParameterDef("preprocessor_defines",
											"Preprocessor defines use to compile the program.",
											PT_STRING), &msCmdPreprocessorDefines);
			dict->addParameter(ParameterDef("use_optimiser",
											"Should the GLSL optimiser be used. Default is true.",
											PT_BOOL), &msCmdOptimisation);
		}
		
		mSyntaxCode = "glsles";
	}
	
	GLSLESProgram::~GLSLESProgram()
	{
		if (isLoaded())
		{
			unload();
		}
		else
		{
			unloadHighLevel();
		}
	}
	
	void GLSLESProgram::loadFromSource(void)
	{
	}
	
	bool GLSLESProgram::compile(const bool checkErrors)
	{
		if (mCompiled == 1)
		{
			return true;
		}
		if (isSupported())
		{
			GLenum shaderType = 0x0000;
			if (mType == GPT_VERTEX_PROGRAM)
			{
				shaderType = GL_VERTEX_SHADER;
			}
			else if (mType == GPT_FRAGMENT_PROGRAM)
			{
				shaderType = GL_FRAGMENT_SHADER;
			}
			mGLHandle = glCreateShader(shaderType);
		}
		
		CPreprocessor cpp;
		if (!mPreprocessorDefines.empty())
		{
			std::string::size_type pos = 0;
			while (pos != std::string::npos)
			{
				std::string::size_type endPos = mPreprocessorDefines.find_first_of(";,=", pos);
				if (endPos != std::string::npos)
				{
					std::string::size_type macro_name_start = pos;
					size_t macro_name_len = endPos - pos;
					pos = endPos;
					
					if (mPreprocessorDefines[pos] = '=')
					{
						++pos;
						std::string::size_type macro_val_start = pos;
						size_t macro_val_len;
						
						endPos = mPreprocessorDefines.find_first_of(";,", pos);
						if (endPos == std::string::npos)
						{
							macro_val_len = mPreprocessorDefines.size() - pos;
							pos = endPos;
						}
						else
						{
							macro_val_len = endPos - pos;
							pos = endPos + 1;
						}
						cpp.Define(
							mPreprocessorDefines.c_str() + macro_name_start, macro_name_len,
								   mPreprocessorDefines.c_str() + macro_val_start, macro_val_len
						);
					}
					else
					{
						++pos;
						cpp.Define(
							mPreprocessorDefines.c_str() + macro_name_start, macro_name_len, 1);
					}
				}
				else
					pos = endPos;
			}
		}
		
		size_t out_size = 0;
		const char* src = mSource.c_str();
		size_t src_len = mSource.size();
		char* out = cpp.Parse(src, src_len, out_size);
		if (!out || !out_size)
			PVM_EXCEPT("Failed to preprocess shader " + mName,
					   __FUNCTION__);
		
		mSource = std::string(out, out_size);
		if (out < src || out > src + src_len)
			free(out);
		
		if (!mSource.empty())
		{
			const char* source = mSource.c_str();
			glShaderSource(mGLHandle, 1, &source, NULL);
		}
		if (checkErrors)
			logObjectInfo("GLSL ES compiling: " + mName, mGLHandle);
		
		glCompileShader(mGLHandle);
		glGetShaderiv(mGLHandle, GL_COMPILE_STATUS, &mCompiled);
		if (!mCompiled && checkErrors)
		{
			std::string message = logObjectInfo("GLSL ES compile log: " + mName, mGLHandle);
			checkAndFixInvalidDefaultPrecisionError(message);
		}
		
		if (mCompiled && checkErrors)
			logObjectInfo("GLSL ES compiled: " + mName, mGLHandle);
		
		return (mCompiled == 1);
	}
	
	void GLSLESProgram::createLowLevelImpl(void)
	{
		mAssemblerProgram = GpuProgramPtr(new GLSLESGpuProgram(this));
	}
	
	void GLSLESProgram::unloadImpl()
	{
		mAssemblerProgram.setNull();
		
		unloadHighLevel();
	}
	
	void GLSLESProgram::unloadHighLevelImpl(void)
	{
		if (isSupported())
		{
			glDeleteProgram(mGLHandle);
		}
	}
	
	void GLSLESProgram::populateParameterNames(GpuProgramParametersSharedPtr params)
	{
		getConstantDefinitions();
		params->_setNamedConstants(mConstantDefs);
	}
	
	void GLSLESProgram::buildConstantDefinitions() const
	{
		createParameterMappingStructures(true);
		GLSLESLinkProgramManager::getSingleton().extractConstantDefs(
			mSource, *mConstantDefs.get(), mName);
	}
	
	inline bool GLSLESProgram::getPassSurfaceAndLightStates(void) const
	{
		return true;
	}
	
	inline bool GLSLESProgram::getPassTransformStates(void) const
	{
		return true;
	}
	
	inline bool GLSLESProgram::getPassFogStates(void) const
	{
		return true;
	}
	
	std::string GLSLESProgram::CmdOptimisation::doSet(const void* target) const
	{
		return StringConverter::toString(static_cast<const GLSLESProgram*>(target)->getOptimiserEnabled());
	}
	void GLSLESProgram::CmdOptimisation::doSet(void* target, const std::string& val)
	{
		static_cast<GLSLESProgram*>(target)->setOptimiserEnabled(StringConverter::parseBool(val));
	}
	
	std::string GLSLESProgram::CmdPreprossorDefines::doGet(const void* target) const
	{
		return static_cast<const GLSLESProgram*>(target)->getPreprocessorDefines();
	}
	void GLSLESProgram::CmdPreprossorDefines::doSet(void* target, const std::string& val)
	{
		static_cast<GLSLESProgram*>(target)->setPreprocessorDefines(val);
	}
	
	void GLSLESProgram::attachToProgramObject(const GLuint programObject)
	{
		glAttachShader(programObject, mGLHandle);
	}
	
	void GLSLESProgram::detachFromProgramObject(const GLuint programObject)
	{
		glDetachShader(programObject, mGLHandle);
	}
	
	const std::string& GLSLESProgram::getLanguage(void) const
	{
		static const std::string language = "glsles";
		return language;
	}
	
	PVM::GpuProgramParametersSharedPtr GLSLESProgram::createParameters(void)
	{
		GpuProgramParametersSharedPtr params = HighLevelGpuProgram::createParameters();
		params->setTransposeMatrices(true);
		return params;
	}
	
	void GLSLESProgram::checkAndFixInvalidDefaultPrecisionError(std::string& message)
	{
		std::string precisionQualifierErrorString = ": 'Default Precision Qualifier' : invalid type Type for default precision qualifier can be only float or int";
		std::vector<std::string>::type linesOfSource = StringUtil::split(mSource, "\n");
		if (message.find(precisionQualifierErrorString) != std::string::npos)
		{
			LogManager::getSingleton().logMessage("Fixing invalid type Type for default precision qualifier by deleting bad lines the re-compiling");
			
			std::vector<std::string>::type errors = StringUtil::split(message, "\n");
			for (size_t i = errors.size() - 1; i != -1; i--)
			{
				std::string& curError = errors[i];
				size_t foundPos = curError.find(precisionQualifierErrorString);
				if (foundPos != std::string::npos)
				{
					std::string lineNumber = curError.substr(0, foundPos);
					size_t posOfStartOfNumber = lineNumber.find_last_of(':');
					if (posOfStartOfNumber != std::string::npos)
					{
						lineNumber = lineNumber.substr(posOfStartOfNumber + 1, lineNumber.size() - (posOfStartOfNumber + 1));
						if (StringConverter::isNumber(lineNumber))
						{
							int iLineNumber = StringConverter::parseInt(lineNumber);
							linesOfSource.erase(linesOfSource.begin() + iLineNumber - 1);
						}
					}
				}
			}
			
			std::stringstream newSource;
			for (size_t i = 0; i < linesOfSource.size(); i++)
			{
				newSource << linesOfSource[i] << "\n";
			}
			mSource = newSource.str();
			
			const char* source = mSource.c_str();
			glShaderSource(mGLHandle, 1, &source, NULL);
			if (compile())
			{
				LogManager::getSingleton().logMessage("The removing of the lines fixed the invalid type Type for default precision qualifier error.");
			}
			else
			{
				LogManager::getSingleton().logMessage("The removing of the lines didn't help.");
			}
		}
	}
	
	RenderOperation::OperationType parseOperationType(const std::string& val)
	{
		if (val == "point_list")
		{
			return RenderOperation::OT_POINT_LIST;
		}
		else if (val == "line_list")
		{
			return RenderOperation::OT_LINE_LIST;
		}
		else if (val == "line_strip")
		{
			return RenderOperation::OT_LINE_STRIP;
		}
		else if (val == "triangle_strip")
		{
			return RenderOperation::OT_TRIANGLE_STRIP;
		}
		else if (val == "triangle_fan")
		{
			return RenderOperation::OT_TRIANGLE_FAN;
		}
		else
		{
			return RenderOperation::OT_TRIANGLE_LIST;
		}
	}
	
	std::string operationTypeToString(RenderOperation::OperationType val)
	{
		switch(val)
		{
			case RenderOperation::OT_POINT_LIST:
				return "point_list";
				break;
			case RenderOperation::OT_LINE_LIST:
				return "line_list";
				break;
			case RenderOperation::OT_LINE_STRIP:
				return "line_strip";
				break;
			case RenderOperation::OT_TRIANGLE_STRIP:
				return "triangle_strip";
				break;
			case RenderOperation::OT_TRIANGLE_FAN:
				return "triangle_fan";
				break;
			case RenderOperation::OT_TRIANGLE_LIST:
			default:
				return "triangle_list";
				break;
		}
	}
}
